<?php
/**
 * @author Jumper Swordwave
 * date: 2021/04/19
 */
declare(strict_types=1);
namespace App\Game\Model;

use Closure;
use App\Game\Core\GameStatus;

/**
 * 房间
 * @property int    $id
 * @property int    $status     房间状态
 * @property int    $master     地主位置
 * @property int    $current    出牌人位置
 * @property int    $score      地主分
 * @property array  $users      玩家id列表
 * @property int    $limit      玩家人数
 * @property array  $hands      用户手牌
 * @property array  $holes      底牌
 * @property array  $outs       上家出牌
 * @property array  $call_list  叫地主列表
 * @property int    $pre        上一位出牌人位置
 */
class Room extends Base
{
    public function __construct(array $fills = [])
    {
        parent::__construct($fills);
        $this->initRoom();
    }

    /**
     * 初始化房间数据
     */
    protected function initRoom()
    {
        $users = [];
        $hands = [];
        for($i=0; $i<$this->limit; $i++) {
            $users[] = 0;
            $hands[] = [];
        }
        $this->users = $users;
        $this->hands = $hands;
        $this->holes = [];
        $this->outs = [];
        $this->call_list = [];
        $this->score = 0;
        $this->pre = 0;
        $this->current = 0;
        $this->master = 0;
        $this->status = GameStatus::ROOM_WAITING;
    }

    /**
     * 重置房间数据
     */
    public function reset()
    {
        $this->initRoom();
    }

    /**
     * 用户是否在本房间内
     * @param int   $uid
     * @return bool
     */
    public function hasUser(int $uid) : bool 
    {
        foreach($this->users as $_id) {
            if ($uid == $_id) return true;
        }
        return false;
    }

    /**
     * 房间玩家数量是否已满
     */
    public function isFull() : bool
    {
        $count = 0;
        foreach($this->users as $val) {
            if ($val != 0) $count++;
        }
        return $count == $this->limit;
    }

    /**
     * 用户是否能加入房间
     */
    public function canJoin() : bool
    {
        if ($this->isFull()) return false;  //人数已满
        if ($this->status != GameStatus::ROOM_WAITING) return false;    //不在等待玩家状态
        return true;
    }

    /**
     * 用户是否能离开房间
     */
    public function canLeave() : bool 
    {
        if ($this->status == GameStatus::ROOM_WAITING
            || $this->status == GameStatus::ROOM_GAMEOVER) {
            return true;
        }
        return false;
    }

    /**
     * 加入用户
     */
    public function join(int $uid) : int
    {
        if (!$this->canJoin()) return 0;
        $pos = 0;
        $users = $this->users;
        foreach($users as $idx=>$val) {
            if ($val == 0) {
                $users[$idx] = $uid;
                $pos = $idx + 1;
                break;
            }
        }
        $this->users = $users;
        if ($this->isFull()) {
            $this->status = GameStatus::ROOM_READY;
        }
        return $pos;
    }

    /**
     * 移除用户
     */
    public function leave(int $uid) : bool 
    {
        if (! $this->canLeave()) return false;
        $users = $this->users;
        foreach($this->users as $idx=>$_id) {
            if ($_id == $uid) {
                $users[$idx] = 0;
                $this->users = $users;
                return true;
            }
        }
        
        return false;
    }

    /**
     * 读取对应位置用户手牌
     * @param int   $position   位置
     * @return array
     */
    public function getHands(int $position) : array 
    {
        $hands = $this->hands;
        if (isset($hands[$position - 1])) {
            return $hands[$position - 1];
        }
        return [];
    }

    /**
     * 返回当前位置用户id
     * @return int  
     */
    public function getCurrentUid() : int
    {
        $users = $this->users;
        return $users[$this->current - 1];
    }

    /**
     * 返回玩家位置
     * @param int   $uid
     * @return int  位置,0未在房间内
     */
    public function getUserPosition(int $uid) : int
    {
        $users = $this->users;
        foreach($users as $idx=>$_id) {
            if ($_id == $uid) {
                return $idx + 1;
            }
        }
        return 0;
    }

    /**
     * 获取用户id
     * @param int   $position   位置id
     * @return int  用户id
     */
    public function getUserId(int $position) : int
    {
        $position--;
        $users = $this->users;
        return isset($users[$position]) ? $users[$position] : 0;
    }

    /**
     * 获取地主用户id
     * @return int  用户id
     */
    public function getMasterUid() : int
    {
        if ($this->master > 0) {
            return $this->getUserId($this->master);
        }
        return 0;
    }

    /**
     * 是否当前用户
     */
    public function isCurrentUser(int $uid) : bool 
    {
        return $uid == $this->getCurrentUid();
    }

    /**
     * 初始化玩家手牌和底牌
     * 根据手牌找出叫地主的位置
     * 房间进入叫地主状态
     */
    public function initCards(array $hands, array $holes)
    {
        $this->hands = $hands;
        sort($holes);
        $this->holes = $holes;
        $this->current = rand(1, $this->limit);
        $this->master = $this->current; //默认出牌人员为地主
        $this->status = GameStatus::ROOM_CALL;
        $this->call_list = [];
        $this->outs = [];
        $this->pre = 0;
    }

    /**
     * 对应位置是否可以叫地主
     * @param int   $position
     * @return bool
     */
    public function canCall(int $position) : bool
    {
        if ($this->isCallOver()) return false;  //地主已产生
        if ($this->status != GameStatus::ROOM_CALL) return false;   //房间状态错误
        if ($this->current != $position) return false;  //当前位置尚未轮到
        return true;
    }

    /**
     * 对应位置是否可出牌
     * @param int   $position
     * @return bool
     */
    public function canOut(int $position) : bool 
    {
        if ($this->status != GameStatus::ROOM_PLAYING) return false;
        if ($this->current != $position) return false;
        return true;
    }

    /**
     * 叫地主
     * @param int   $position   用户位置
     * @param bool  $call       是否要抢地主
     * @return int  当前地主位置
     */
    public function call(int $position, $call) : bool
    {
        if (!$this->canCall($position)) return false;   //

        if ($call) {
            $this->master = $position;
            $this->score++;
        }
        $call_list = $this->call_list;
        $call_list[] = $position;
        $this->call_list = $call_list;

        if ($this->isCallOver()) {  //最后一位叫地主
            $this->status = GameStatus::ROOM_PLAYING;
            //地主拿取底牌
            $hands = $this->hands;
            $hands[$this->master - 1] = array_merge($hands[$this->master - 1], $this->holes);
            sort($hands[$this->master - 1]);
            $this->hands = $hands;
            //地主出牌
            $this->current = $this->master;
            $this->pre = 0;
        } else {
            $this->next();
        }

        return true;
    }

    /**
     * 叫地主是否结束
     * @return bool 
     */
    public function isCallOver() : bool
    {
        $call_list = $this->call_list;
        return count($call_list) == $this->limit;
    }

    /**
     * 是否起手出牌玩家
     * @param int   $position
     * @return bool
     */
    public function isFirstOut(int $position) : bool
    {
        return $this->current == $position && ($this->pre == 0 || $this->pre == $position);
    }

    /**
     * 检查用户是否有这些手牌
     * @param int   $pos 
     * @param array $outs
     * @return bool
     */
    public function checkHands(int $pos, array $outs) : bool
    {
        $hands = $this->getHands($pos);
        foreach($outs as $card) {
            if (!in_array($card, $hands)) return false;
        }
        return true;
    }

    /**
     * 更新出牌信息
     * @param int   $pos        出牌人位置
     * @param array $outs       出牌
     * @param Closure   $logic  判断逻辑
     * @return bool     出牌是否成功
     */
    public function outs(int $pos, array $outs, Closure $logic) : bool
    {
        if (!$this->canOut($pos)) return false;
        if (!$this->checkHands($pos, $outs)) return false;

        $pre_outs = $this->outs;
        if ($this->pre == $pos) {   //当前出牌人和上一出牌人是同一个时
            $pre_outs = [];
        }
        if (! $logic($outs, $pre_outs)) return false;   //出牌失败

        //去除玩家手牌
        $hands = $this->hands;
        $hands[$pos - 1] = array_diff($hands[$pos - 1], $outs);
        sort($hands[$pos - 1]);
        $this->hands = $hands;

        //成功进入下一轮
        $this->outs = $outs;
        $this->next();
        return true;
    }

    /**
     * 玩家跳过出牌
     * @param int   $pos
     * @return bool
     */
    public function skip(int $pos) : bool 
    {
        if (!$this->canOut($pos)) return false;
        if ($this->pre == $pos) return false;   //上一轮出牌人是同一玩家,不能跳过出牌

        $this->next(false);
        return true;
    }

    /**
     * 转入下一位置
     * @param bool  $update_pre 是否更新pre位置信息
     */
    public function next(bool $update_pre = true) : void
    {
        if ($update_pre) {
            $this->pre = $this->current;
        }
        $this->current++;
        if ($this->current > $this->limit) {
            $this->current = 1;
        }
    }

    /**
     * 读取胜利用户id
     * @return int  未产生返回0,反之返回胜利者的用户id
     */
    public function winUser() : int
    {
        $hands = $this->hands;
        $users = $this->users;
        foreach($hands as $idx=>$cards) {
            if (empty($cards)) {
                return $users[$idx];
            }
        }
        return 0;
    }

    /**
     * 胜利方
     * @return int  1:地主胜/2:长工胜
     */
    public function winType() : int 
    {
        $win_id = $this->winUser();
        return $win_id == $this->getUserId($this->master) ? 1 : 2;
    }

    /**
     * 游戏结束
     */
    public function gameover() : bool
    {
        $this->status = GameStatus::ROOM_GAMEOVER;
        return true;
    }

    /**
     * 返回房间公开信息
     */
    public function info() : array
    {
        return [
            'id' => $this->id,
            'status' => $this->status,
            'master' => $this->master,
            'current' => $this->current,
            'score' => $this->score,
            'holes' => $this->holes,
            'outs' => $this->outs,
            'pre' => $this->pre,
        ];
    }
}